from ..log import log_gui
from item import BaseFormItem
from ..rect import Rect
from ..background import ShapeBackground
from ..app import GuiApp
import gtk
import pango

def DrawRedRectangle(window, x, y, w, h):
  cmap = window.get_colormap()
  color = cmap.alloc_color('#FF0000')
  colorgc = window.new_gc()
  colorgc.set_foreground(color)
  colorgc.set_fill(gtk.gdk.SOLID)

  window.draw_rectangle(
      colorgc, True, x, y, w, h)

def EscapeHtml(str):
  return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")

################################################################################
class BaseButton(BaseFormItem):
  def __init__(self, x, y, width, height):
    BaseFormItem.__init__(self, x, y, width, height)
    self._highlighted = False
    self._cache = {}
    self._cache_rect = Rect((0, 0, 0, 0))
    self._background = None
    self._momentary = False
    self._down = False

  def down(self):
    """Returns true if the button is currently held down. Useful for
    momentary buttons."""
    return self._down

  def SetMomentary(self, value):
    """Run OnTap when the button is pressed down and ALSO when it is
    released if value is True."""
    self._momentary = value

  def ResetCache(self):
    self._cache = {}
    self._cache_rect = Rect((0, 0, 0, 0))

  def IsHighlighted(self):
    return self._highlighted

  def SetHighlighted(self, value):
    self._highlighted = value

  def OnButtonDown(self, form, event, data, coords):
    log_gui("BaseButton.OnButtonDown", self)
    self.SetHighlighted(True)
    self.Update()
    form.parent.Paint()
    self._down = True
    if self._momentary:
      self.OnTap()

  def OnButtonUp(self, form, event, data, coords):
    log_gui("Button got OnButtonUp")
    self.SetHighlighted(False)
    self.Update()
    form.Paint()
    log_gui("  Calling OnTap")
    self._down = False
    self.OnTap()

  def OnMouseMove(self, form, event, data, coords):
    pass

  def ResetBackground(self):
    self._background = None

  def SaveBackgroundIfNeeded(self, window, rect):
    if self._background is None:
      log_gui("Button.Draw: Grabbing background.")
      self._background = gtk.gdk.Pixmap(
          window, self.GetWidth(), self.GetHeight(), -1)
      self.CopyWindowToCache(window, rect, self._background)

  def Draw(self, screen, window, rect, clip_rect=None):
    if (self._cache_rect.width != self.GetWidth() or
        self._cache_rect.height != self.GetHeight()):
      # This happens when a screen is scrolling in. Its background is messed
      # up, as are the foreground images.
      log_gui("Button.Draw: Resize occurred. Invalidating cache.")
      for hl in self._cache.keys():
        del self._cache[hl]
      self.ResetBackground()
      self._cache_rect = Rect((0, 0, self.GetWidth(), self.GetHeight()))

    self.SaveBackgroundIfNeeded(window, rect)

    if self._highlighted not in self._cache:
      # Initialize all caches before drawing to the window so that all the
      # images can be transparent to the original window, not the other
      # images.
      for hl in (True, False):
        if hl not in self._cache:
          self._cache[hl] = gtk.gdk.Pixmap(
              window, self.GetWidth(), self.GetHeight(), -1)
          self.CopyBackgroundToCache(self._cache[hl], window)
          log_gui("Button.Draw: Calling draw to cache for state", hl)
          self.DrawToCache(screen, self._cache[hl], hl)

    log_gui("Button.Draw: Copy cache to window.")
    self.CopyCacheToWindow(self._cache[self._highlighted], window, rect,
        clip_rect)

    if clip_rect:
      log_gui("Button.Draw: Have a clip rect. Invalidating cache.", clip_rect)
      for hl in self._cache.keys():
        del self._cache[hl]
      self.ResetBackground()
      self._cache_rect = Rect((0, 0, 0, 0))

  def DrawToCache(self, screen, cache, state):
    pass

  def CopyBackgroundToCache(self, cache, window):
    gc = gtk.gdk.GC(window)
    cache.draw_drawable(
        gc,
        self._background,
        0,
        0,
        0,
        0,
        self.rect.width,
        self.rect.height)

  def CopyWindowToCache(self, window, rect, cache):
    gc = gtk.gdk.GC(window)
    DrawRedRectangle(cache, 0, 0, 800, 480)
    cache.draw_drawable(
        gc,
        window,
        rect.left + self.rect.left,
        rect.top + self.rect.top,
        0,
        0,
        self.rect.width,
        self.rect.height)

  def CopyCacheToWindow(self, cache, window, rect, clip_rect):
    # Clip rect may be null if the draw in response to, e.g., button down
    gc = gtk.gdk.GC(window)
    if clip_rect:
      gc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    window.draw_drawable(
        gc,
        cache,
        0,
        0,
        rect.left + self.rect.left,
        rect.top + self.rect.top,
        self.rect.width,
        self.rect.height)

  def Update(self):
    pass

  def OnTap(self):
    log_gui("*** TAP ON",self)
    pass

################################################################################
class BasicTextButton(BaseButton):
  HORIZONTAL_MARGIN = 5
  VERTICAL_MARGIN = 2

  WEIGHT_NORMAL = None
  WEIGHT_BOLD = "bold"

  def __init__(self, screen, x, y, background, hl_background, caption,
      text_attrs, hl_text_attrs, callback, left_margin=HORIZONTAL_MARGIN,
      right_margin=HORIZONTAL_MARGIN, top_margin=VERTICAL_MARGIN,
      bottom_margin=VERTICAL_MARGIN):
    self.text_attrs = {}
    self.text_attrs[False] = dict(text_attrs)
    self.text_attrs[True] = dict(hl_text_attrs)
    self.caption = caption
    self.callback = callback
    self.background = {}
    self.background[False] = background
    self.background[True] = hl_background
    self.left_margin = left_margin
    self.top_margin = top_margin
    self._layout = {}
    self._layout[False] = BasicTextButton.CreateLayout(
        screen, caption, self.text_attrs[False])
    self._layout[True] = BasicTextButton.CreateLayout(
        screen, caption, self.text_attrs[True])
    w1, h1 = self._layout[False].get_pixel_size()
    w2, h2 = self._layout[True].get_pixel_size()
    h1 += top_margin + bottom_margin
    h2 += top_margin + bottom_margin
    w = max(w1, w2, background.GetMinWidth(), hl_background.GetMinWidth())
    h = max(h1, h2, background.GetMinHeight(), hl_background.GetMinHeight())
    BaseButton.__init__(self, x, y + top_margin, w + left_margin + right_margin,
        h)

  def __str__(self):
    return "<BasicTextButton caption=%s>" % self.caption

  def DrawToCache(self, screen, cache, state):
    assert cache is not None
    self.background[state].Draw(
        cache, Rect((0, 0, self.GetWidth(), self.GetHeight())), None)
    self.DrawForeground(cache, state)

  def DrawForeground(self, window, state):
    gc = window.new_gc()
    window.draw_layout(gc, self.left_margin, self.top_margin,
        self._layout[state])

  def OnTap(self):
    self.callback()

  @staticmethod
  def CreateLayout(screen, caption, text_attrs):
    log_gui("CreateLayout: screen is", screen)
    gc = screen.GC(screen.window())
    context = screen.app().create_pango_context()
    if text_attrs.get("weight", None) == BasicTextButton.WEIGHT_BOLD:
      b = "<b>"
      end_b = "</b>"
    else:
      b = ""
      end_b = ""
    attr_info, layout_info, accelerator = pango.parse_markup(
        '<span foreground="' + text_attrs.get('fg_color', '#000000') +
        '">' + b + EscapeHtml(caption) + end_b + '</span>', u'\x00')
    layout = screen.app().create_pango_layout(layout_info)
    layout.set_attributes(attr_info)
    desc = pango.FontDescription('%s %d' % (text_attrs.get('font', 'Helvetica'),
      text_attrs.get('size', 12)))
    log_gui("ATTRS",text_attrs)
    layout.set_font_description(desc)
    return layout

################################################################################
class BackButton(BasicTextButton):
  def __init__(self, app, screen, caption, dest_name, x=0, y=0, pre_cb=None):
    self.app = app
    self.dest_name = dest_name
    self.pre_cb = pre_cb
    path = 'gui/assets/back_button/'
    BasicTextButton.__init__(self, screen, x, y,
        ShapeBackground(
          path + 'left.png',
          path + 'middle.png',
          path + 'right.png'),
        ShapeBackground(
          path + 'left_sel.png',
          path + 'middle_sel.png',
          path + 'right_sel.png'),
        caption,
        { 'fg_color': '#FFFFFF',
          'size': 16 },
        { 'fg_color': '#FFFFFF',
          'size': 16 },
        None,
        left_margin=25,
        right_margin=15,
        top_margin=10)

  def SetDestName(self, dest_name):
    self.dest_name = dest_name

  def OnTap(self):
    if self.pre_cb:
      self.pre_cb()
    self.app.SetCurrentScreen(self.dest_name, GuiApp.ENTER_FROM_LEFT, None)

################################################################################
class ImageButton(BaseButton):
  def __init__(self, screen, x, y, image, hl_image, callback):
    self.callback = callback
    self.image = {}
    self.image[False] = image
    self.image[True] = hl_image
    w = max(image.GetMinWidth(), hl_image.GetMinWidth())
    h = max(image.GetMinHeight(), hl_image.GetMinHeight())
    BaseButton.__init__(self, x, y, w, h)

  def DrawToCache(self, screen, cache, state):
    assert cache is not None
    log_gui("*** DRAW IMAGE BUTTON TO CACHE", self.IsHighlighted())
    self.image[state].Draw(
        cache, Rect((0, 0, self.GetWidth(), self.GetHeight())), None)

  def OnTap(self):
    self.callback()


################################################################################
class MultiStateButton(BaseButton):
  def __init__(self, screen, x, y, buttons, initial_state):
    """buttons is a dict {state: BaseButton, ...} for all possible states.
    A state is a frozenset() of arbitrary values."""
    w = max(*map(lambda b: b.GetWidth(), buttons.itervalues()))
    h = max(*map(lambda b: b.GetHeight(), buttons.itervalues()))
    BaseButton.__init__(self, x, y, w, h)
    self._state = initial_state
    self._buttons = dict(buttons)

  def SetState(self, state):
    if state == self._state:
      return
    log_gui("MultiStateButton: New state is", state)
    self._state = state
    self.ResetCache()
    self.GetForm().parent.Paint()

  def DrawToCache(self, screen, cache, state):
    assert cache is not None
    log_gui("*** DRAW IMAGE BUTTON TO CACHE", self._state)
    self._buttons[self._state].DrawToCache(screen, cache, state)

  def OnTap(self):
    self._buttons[self._state].OnTap()

